﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

using ChessKit.Properties;

namespace ChessKit.Logic
{
	[PublicApi]
	public static class Pieces
	{
		public static bool Is(this Piece piece, PieceColor color)
		{
			return ((MoveHints)piece & (MoveHints)color) != 0;
		}
		public static bool Is(this Piece piece, PieceType type)
		{
			return ((MoveHints)piece & (MoveHints)type) != 0;
		}
		public static PieceColor GetColor(this Piece piece)
		{
			return (PieceColor)((MoveHints)piece & MoveHints.AllColors);
		}
		public static PieceType GetPieceType(this Piece piece)
		{
			return (PieceType)((MoveHints)piece & MoveHints.AllPieces);
		}
		public static ReadOnlyCollection<Direction> GetDirections(this Piece piece)
		{
			return Directions[(int)piece];
		}
		public static PieceColor Invert(this PieceColor color)
		{
			return color == PieceColor.White ? PieceColor.Black : PieceColor.White;
		}
		public static ReadOnlyCollection<Piece> All { get; private set; }
		public static Piece ToPiece(this PieceType type, PieceColor color)
		{
			return (Piece)((MoveHints)type | (MoveHints)color);
		}
		public static Piece ToPiece(this PieceColor color, PieceType type)
		{
			return (Piece)((MoveHints)type | (MoveHints)color);
		}

		public static int GetIndex(this Piece piece)
		{
			return Indexes[(int)piece];
		}
		public static char ToEnglishSymbol(this PieceType piece)
		{
			return EnglishSymbols[(int)piece.ToPiece(PieceColor.White)];
		}
		public static char ToEnglishSymbol(this Piece piece)
		{
			return EnglishSymbols[(int)piece];
		}
		public static char ToChessSymbol(this Piece piece)
		{
			return ChessSymbols[(int)piece];
		}
		public static string ToRussianSymbol(this Piece piece)
		{
			return RussianSymbols[(int)piece];
		}
		/// <summary>Parses a symbol into a Piece.
		///   Recognizes japanese hieroglyphs as well as latin symbols</summary>
		public static Piece EnglishSymbolToPiece(this char symbol)
		{
			var idx = symbol - 'A';
			if (idx < 0 || idx >= PieceByEnglishSymbol.Length)
				throw new FormatException("illegal character: " + symbol);
			var piece = PieceByEnglishSymbol[idx];
			if (piece == 0)
				throw new FormatException("illegal character: " + symbol);
			return piece;
		}

		/// <summary>Tries to parse a symbol into a PieceType.
		///   Recognizes japanese hieroglyphs as well as latin symbols</summary>
		/// <returns>false if it couldn't parse the symbol</returns>
		public static bool TryEnglishSymbolToPiece(this char symbol, out Piece result)
		{
			var idx = symbol - 'A';
			if (idx < 0 || idx >= PieceByEnglishSymbol.Length)
			{
				result = 0;
				return false;
			}
			result = PieceByEnglishSymbol[idx];
			return result != 0;
		}

		static Pieces()
		{
			All = new ReadOnlyCollection<Piece>(new[]
				{
					Piece.WhitePawn, Piece.WhiteBishop, Piece.WhiteKnight, Piece.WhiteRook,
					Piece.WhiteQueen, Piece.WhiteKing, Piece.BlackPawn, Piece.BlackBishop,
					Piece.BlackKnight, Piece.BlackRook, Piece.BlackQueen, Piece.BlackKing
				});

			var englishSymbols = new[]
				{
					'P', 'B', 'N', 'R', 'Q', 'K',
					'p', 'b', 'n', 'r', 'q', 'k'
				};
			var chessSymbols = new[]
				{
					'♙', '♗', '♘', '♖', '♕', '♔',
					'♟', '♝', '♞', '♜', '♛', '♚'
				};
			var russianSymbols = new[]
				{
					"П", "С", "К", "Л", "Ф", "Кр",
					"п", "с", "к", "л", "ф", "кр"
				};
			var directions = new[]
				{
					Join(Move(0, 1), Take(1, 1), Take(-1, 1),
					     Special(0, 2)),

					Join(Slide(1, 1), Slide(1, -1),
					     Slide(-1, -1), Slide(-1, 1)),

					Join(Take(1, 2), Take(-1, 2), Take(1, -2),
					     Take(-1, -2), Take(2, 1), Take(-2, 1),
					     Take(2, -1), Take(-2, -1)),

					Join(Slide(0, 1), Slide(1, 0), Slide(0, -1),
					     Slide(-1, 0)),

					Join(Slide(1, 1), Slide(1, -1),
					     Slide(-1, -1), Slide(-1, 1),
					     Slide(0, 1), Slide(1, 0), Slide(0, -1),
					     Slide(-1, 0)),

					Join(Take(0, 1), Take(1, 1), Take(1, 0),
					     Take(1, -1),
					     Take(0, -1), Take(-1, -1), Take(-1, 0),
					     Take(-1, 1),
					     Special(2, 0), Special(-2, 0)),

					Join(Move(0, -1), Take(1, -1), Take(-1, -1),
					     Special(0, -2)),
					Join(Slide(1, 1), Slide(1, -1),
					     Slide(-1, -1), Slide(-1, 1)),

					Join(Take(1, 2), Take(-1, 2), Take(1, -2),
					     Take(-1, -2), Take(2, 1), Take(-2, 1),
					     Take(2, -1), Take(-2, -1)),

					Join(Slide(0, 1), Slide(1, 0), Slide(0, -1),
					     Slide(-1, 0)),

					Join(Slide(1, 1), Slide(1, -1),
					     Slide(-1, -1), Slide(-1, 1),
					     Slide(0, 1), Slide(1, 0), Slide(0, -1),
					     Slide(-1, 0)),

					Join(Take(0, 1), Take(1, 1), Take(1, 0),
					     Take(1, -1),
					     Take(0, -1), Take(-1, -1), Take(-1, 0),
					     Take(-1, 1),
					     Special(2, 0), Special(-2, 0))
				};
			PieceByEnglishSymbol = new Piece['z' - 'A'];
			EnglishSymbols = new char[(int)Piece.BlackKing + 1];
			ChessSymbols = new char[(int)Piece.BlackKing + 1];
			RussianSymbols = new string[(int)Piece.BlackKing + 1];
			Indexes = new int[(int)Piece.BlackKing + 1];
			Directions = new ReadOnlyCollection<Direction>[(int)Piece.BlackKing + 1];
			for (var i = 0; i < All.Count; i++)
			{
				var englishSymbol = englishSymbols[i];
				PieceByEnglishSymbol[englishSymbol - 'A'] = All[i];
				EnglishSymbols[(int)All[i]] = englishSymbol;
				ChessSymbols[(int)All[i]] = chessSymbols[i];
				RussianSymbols[(int)All[i]] = russianSymbols[i];
				Directions[(int)All[i]] = directions[i];
				Indexes[(int)All[i]] = i;
			}
		}

		private static readonly Piece[] PieceByEnglishSymbol;
		private static readonly char[] EnglishSymbols;
		private static readonly char[] ChessSymbols;
		private static readonly string[] RussianSymbols;
		private static readonly ReadOnlyCollection<Direction>[] Directions;
		private static readonly int[] Indexes;

		#region ' MoveDirection '

		private static ReadOnlyCollection<T> Join<T>(params IEnumerable<T>[] arr)
		{
			return new ReadOnlyCollection<T>(arr.SelectMany(e => e).ToList());
		}

		private static IEnumerable<Direction> Slide(int dx, int dy)
		{
			yield return new Direction(dx, dy, 8, true);
		}
		private static IEnumerable<Direction> Take(int dx, int dy)
		{
			yield return new Direction(dx, dy, 1, true);
		}
		private static IEnumerable<Direction> Move(int dx, int dy)
		{
			yield return new Direction(dx, dy, 1, false);
		}
		private static IEnumerable<Direction> Special(int dx, int dy)
		{
			yield return new Direction(dx, dy, 1, false, isSpecial: true);
		}

		#endregion

	}
}